package cn.elead.chaos.sso.core.filter;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.elead.chaos.sso.core.constant.SsoConstant;
import cn.elead.chaos.sso.core.handler.LoginSuccessHandler;
import cn.elead.chaos.sso.core.handler.LogoutSuccessHandler;
import cn.elead.chaos.sso.core.path.AntPathMatcher;
import cn.elead.chaos.sso.core.user.LoginUser;
import cn.elead.chaos.sso.core.util.HttpUtils;
import cn.elead.chaos.sso.core.util.Response;
import cn.elead.chaos.sso.core.util.SsoUtils;
import cn.elead.chaos.sso.core.wrapper.ModifyHttpServletRequestWrapper;

/**
 * 客户端过滤器
 * 
 * @author luopeng
 *
 */
public abstract class SsoInitFilter implements Filter {

	private static Logger logger = LoggerFactory.getLogger(SsoInitFilter.class);

	private static final AntPathMatcher antPathMatcher = new AntPathMatcher();

	// 登录成功后处理器
	protected LoginSuccessHandler loginSuccessHandler;

	// 登出成功后处理器
	protected LogoutSuccessHandler logoutSuccessHandler;

	// 单点登录服务
	protected String ssoServer;
	// 客户端ID
	protected String clientId;
	// 客户端秘钥
	protected String clientSecret;
	// 单点登录模式
	protected String responseType;
	// 单点登录退出
	protected String logoutPath;
	// 无需单点登录的路径
	protected String excludedPaths;
	// shiro
	protected String xAuthToken;
	// code回调地址
	protected String codeCallback;
	// 获取单点用户信息
	protected String ssoUserPath;
	// 是否校验租户
	protected boolean vaildTenant = false;
	// 租户
	protected String tenantHeader;

	@Override
	public void init(FilterConfig config) throws ServletException {
		ssoServer = emptyToDefault(config, SsoConstant.SSO_SERVER, "");
		clientId = emptyToDefault(config, SsoConstant.PARAM_CLIENT_ID, "");
		clientSecret = emptyToDefault(config, SsoConstant.SSO_CLIENT_SECRET, "");
		xAuthToken = emptyToDefault(config, SsoConstant.AUTHORIZATION, SsoConstant.AUTHORIZATION);
		responseType = emptyToDefault(config, SsoConstant.PARAM_RESPONSE_TYPE, "code");
		excludedPaths = emptyToDefault(config, SsoConstant.SSO_EXCLUDED_PATHS, "/profile/**,/static/**,/druid/**");
		logoutPath = emptyToDefault(config, SsoConstant.SSO_LOGOUT, "/logout");
		codeCallback = emptyToDefault(config, SsoConstant.CODE_CALL_BACK, "/codeCallback");
		ssoUserPath = emptyToDefault(config, SsoConstant.SSO_USER_PATH, "/getSsoUser");
		String vaild = config.getInitParameter(SsoConstant.VAILD_TENANT);
		if (!isEmpty(vaild)) {
			vaildTenant = Boolean.valueOf(vaild);
		}
		tenantHeader = emptyToDefault(config, SsoConstant.TENANT_KEY, "tenant");

	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		// 已经登录, 继续执行
		chain.doFilter(request, response);
	}
	
	/**
	 * 获取token
	 * @param request
	 * @param response
	 * @param isRemote
	 * @return
	 * @throws IOException
	 */
	protected abstract Response<String> getToken(HttpServletRequest request, HttpServletResponse response, boolean isRemote) throws IOException;

	/**
	 * 排除路径
	 * 
	 * @param request
	 * @param response
	 * @param chain
	 * @param servletPath
	 * @throws IOException
	 * @throws ServletException
	 */
	protected boolean excludedPathHandler(HttpServletRequest request, HttpServletResponse response, FilterChain chain, String servletPath) throws IOException, ServletException {

		boolean isNotDoFilter = false;
		if (excludedPaths != null && excludedPaths.trim().length() > 0) {
			LoginUser user = new LoginUser();
			Response<String> res = getToken(request, response, false);
			if(!isnull(res)) {
				user.setToken(res.getData());
				user.setUserId("admin");
			}
			for (String excludedPath : excludedPaths.split(",")) {
				String uriPattern = excludedPath.trim();
				// 支持ANT表达式
				if (antPathMatcher.match(uriPattern, servletPath)) {
					// excluded path, allow
					// 成功后置处理
					postProcessAfterLoginSuccess(request, response, chain, user);
					//chain.doFilter(request, response);
					isNotDoFilter = true;
				}
			}
			
		}
		return isNotDoFilter;
	}

	/**
	 * 成功后置处理器
	 * 
	 * @param request
	 * @param response
	 * @param chain
	 * @param user
	 * @throws IOException
	 * @throws ServletException
	 */
	protected void postProcessAfterLoginSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, LoginUser user) throws IOException, ServletException {

		ModifyHttpServletRequestWrapper requestWrapper = new ModifyHttpServletRequestWrapper(request);
		requestWrapper.putHeader(xAuthToken, user.getToken());
		requestWrapper.setAttribute(SsoConstant.SSO_LOGIN_USER, user);
		requestWrapper.setAttribute(SsoConstant.SSO_LOGIN_USER_ID, user.getUserId());

		// 用户
		SsoUtils.bind(user);

		// 成功处理器
		if (null != loginSuccessHandler) {
			loginSuccessHandler.onLoginSuccess(requestWrapper, response, user);
		}
		try {
			// 访问量
			pv(user.getToken());
			chain.doFilter(requestWrapper, response);
		} catch (Exception e) {
			throw e;
		} finally {
			// 清除用户
			SsoUtils.unbindUser();
			// 成功之后处理器
			if (null != loginSuccessHandler) {
				loginSuccessHandler.onEnd(requestWrapper, response, user);
			}
		}
	}

	/**
	 * 访问量
	 * 
	 * @param token
	 * @throws IOException
	 */
	protected void pv(String token) throws IOException {
		try {
			new Thread(() -> {
				remoteConnect("/redis/pv/" + token, "PUT");
			}).start();
		} catch (Exception e) {
		}
	}

	protected Response<String> remoteConnect(String url, String method) {
		Response<String> body = null;
		try {
			// 通过code换取token
			Map<String, String> headers = new HashMap<>();
			headers.put("clientId", clientId);
			headers.put("clientSecret", clientSecret);
			body = HttpUtils.connect(ssoServer + url).setMethod(method).setCharset("UTF-8").setHeaders(headers).execute().getBody();
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			body = new Response<String>();
			body.setCode(500);
			body.setData(e.getMessage());
		}
		return body;
	}

	protected void toLogin(HttpServletRequest request, HttpServletResponse response) throws IOException {
		// total link
		String link = request.getRequestURL().toString();
		link = link.replace(logoutPath, "");
		// redirect logout
		String logoutPageUrl = ssoServer.concat("/").concat(SsoConstant.LOGIN_PAGE).concat("?") //
				.concat(SsoConstant.PARAM_RESPONSE_TYPE).concat("=").concat(responseType).concat("&") //
				.concat(SsoConstant.PARAM_CLIENT_ID).concat("=").concat(clientId).concat("&") //
				.concat(SsoConstant.REDIRECT_URI).concat("=").concat(link); //
		response.sendRedirect(logoutPageUrl);
	}

	protected void println(HttpServletResponse response, int status, String json) throws IOException {
		response.setStatus(status);
		// json msg
		response.setContentType("application/json;charset=utf-8");
		response.getWriter().println(json);
	}

	protected void println(HttpServletRequest request, HttpServletResponse response, int status, String json) throws IOException {
		response.setStatus(status);
		// json msg
		response.setContentType("application/json;charset=utf-8");
		response.getWriter().println(json);
	}

	public String emptyToDefault(FilterConfig filterConfig, String key, String defaultStr) {
		return emptyToDefault(filterConfig.getInitParameter(key), defaultStr);
	}

	public static String emptyToDefault(CharSequence str, String defaultStr) {
		return isEmpty(str) ? defaultStr : str.toString();
	}

	protected static boolean isEmpty(final CharSequence cs) {
		return cs == null || cs.length() == 0;
	}

	protected static boolean isnull(Object o) {
		return o == null;
	}

	public LoginSuccessHandler getLoginSuccessHandler() {
		return loginSuccessHandler;
	}

	public void setLoginSuccessHandler(LoginSuccessHandler loginSuccessHandler) {
		this.loginSuccessHandler = loginSuccessHandler;
	}

	public LogoutSuccessHandler getLogoutSuccessHandler() {
		return logoutSuccessHandler;
	}

	public void setLogoutSuccessHandler(LogoutSuccessHandler logoutSuccessHandler) {
		this.logoutSuccessHandler = logoutSuccessHandler;
	}

}
